package ar.edu.unlp.info.tesis.controladores.lrs;

import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.log4j.Logger;
import org.aspectj.weaver.bcel.AtAjAttributes;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.hibernate.HibernateException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import ar.edu.unlp.info.tesis.controladores.lrs.exceptions.StatementException;
import ar.edu.unlp.info.tesis.database.exception.DatabaseException;
import ar.edu.unlp.info.tesis.database.service.ActorService;
import ar.edu.unlp.info.tesis.database.service.ContextService;
import ar.edu.unlp.info.tesis.database.service.GenericService;
import ar.edu.unlp.info.tesis.database.service.StatementService;
import ar.edu.unlp.info.tesis.database.service.VerbService;
import ar.edu.unlp.info.tesis.domain.Actor;
import ar.edu.unlp.info.tesis.domain.Attachment;
import ar.edu.unlp.info.tesis.domain.Group;
import ar.edu.unlp.info.tesis.domain.Statement;
import ar.edu.unlp.info.tesis.domain.StatementObject;
import ar.edu.unlp.info.tesis.domain.Verb;


@Controller
@RequestMapping(value = "/statements")
public class StatementAPIController extends ExperienceAPIController{

	final static Logger logger = Logger.getLogger(StatementService.class);

	@RequestMapping(method = RequestMethod.PUT, headers = "Accept=application/json", consumes = {MediaType.APPLICATION_JSON_VALUE})
	@ResponseBody
	public ResponseEntity<String> putMethod(@RequestParam(value="statementId", required=true) String statementId, @RequestBody String jsonStatement) throws Exception
	{
		StatementService service = new StatementService();
		service.verificarStatement(statementId, jsonStatement);
		Statement statement = this.crearStatement(service, jsonStatement);
		service.create(statement);
		return new ResponseEntity<String>(HttpStatus.NO_CONTENT);
	}	

	@RequestMapping(method = RequestMethod.POST, headers = "Accept=application/json", consumes = {MediaType.APPLICATION_JSON_VALUE})
	@ResponseBody
	public ResponseEntity<List<String>> postMethod(@RequestBody String jsonStatements) throws Exception 
	{
		StatementService service = new StatementService();

		ObjectMapper mapper = new ObjectMapper();
		List<JsonNode> nodos = new Vector<JsonNode>();
		List<String> ids = new Vector<String>();

		JsonNode nodo = mapper.readTree(jsonStatements);
		if(nodo.isArray())
		{
			Iterator<JsonNode> elements = nodo.getElements();
			while(elements.hasNext())
				nodos.add(elements.next());
		}
		else
			nodos.add(nodo);

		for(JsonNode n : nodos)
		{
			JsonNode statementId = n.get("id");

			if(statementId != null && !statementId.getTextValue().equals(""))
				service.verificarStatement(statementId.getTextValue(), n.toString());

			Statement statement = this.crearStatement(service, n.toString());
			if(statement.getId() == null || statement.getId().equals(""))
				statement.setId(UUID.randomUUID().toString());

			service.create(statement);
			
			ids.add(statement.getId());
		}

		return new ResponseEntity<List<String>>(ids, HttpStatus.OK);
	}
	
	@RequestMapping(method = RequestMethod.GET, headers = "Accept=application/json")
	@ResponseBody
	public ResponseEntity<Object> getMethod(HttpServletRequest request, @RequestParam(value="statementId", required=false) String statementId, @RequestParam(value="voidedStatementId", required=false) String voidedStatementId,
			@RequestParam(value="verb", required=false) String verb, @RequestParam(value="activity", required=false) String activity,
			@RequestParam(value="registration", required=false) String registration, @RequestParam(value="related_activities", required=false) Boolean related_activities, 
			@RequestParam(value="related_agents", required=false) Boolean related_agents, @RequestParam(value="since", required=false) Date since,
			@RequestParam(value="until", required=false) Date until, @RequestParam(value="limit", required=false) Integer limit,  @RequestParam(value="format", required=false, defaultValue="exact") String format,
			@RequestParam(value="attachments", required=false, defaultValue="false") boolean attachments, @RequestParam(value="ascending", required=false) Boolean ascending, @RequestParam(value="parentId", required=false) String parentId) throws Exception
			{
		Actor agent = null;
		try
		{
			ObjectMapper mapper = new ObjectMapper();
			agent = mapper.readValue(request.getParameter("agent"), Actor.class);
		}
		catch(Exception e){}
		
		if(statementId != null && voidedStatementId != null)
			throw new StatementException("You can specified 'statementId' or 'voidedStatementId' but not both", HttpStatus.BAD_REQUEST);

		if(statementId != null || voidedStatementId != null)
		{
			if(agent != null || verb != null || activity != null || registration != null || related_activities != null || related_agents != null || since != null || until != null || limit != null || ascending != null)
				throw new StatementException("If you specified 'statementId' or 'voidedStatementId' you can not specified any other parameter beside 'format' or 'attachments'", HttpStatus.BAD_REQUEST);
		}
		
		StatementService service = new StatementService();

		if(ascending == null)
			ascending = false;
		
		if(related_activities == null)
			related_activities = false;

		if(related_agents == null)
			related_agents= false;

		if(limit == null)
			limit = 0;

		if(statementId != null)
		{
			Statement statement = (Statement)service.getById(statementId);
			if(!attachments)
			{
				Statement temp = new Statement();
				BeanUtils.copyProperties(temp, statement);
				temp.setAttachments(null);
				return new ResponseEntity<Object>(temp, HttpStatus.OK);
			}
			else
				return new ResponseEntity<Object>(statement, HttpStatus.OK);
		}

		List<Statement> statements = service.getStatements(voidedStatementId, agent, verb, activity, registration, related_activities, related_agents, since, until, limit, ascending, parentId);

		return new ResponseEntity<Object>(statements, HttpStatus.OK);
			}

	/**
	 * Retorna al lista de paraámetros en formato JSon.
	 * 
	 * @param idActor
	 * @return
	 */
	/*Este metodo ya no se usa, para lograr lo mismo hay que invocar /statements?agent={\"id\":\"idgrupo\"}
	 * 
	 * @RequestMapping(value = "listStatements/{idGroup}", method = RequestMethod.GET, headers = "Accept=application/json")
	@ResponseBody
	public List<Statement> listStatement(@PathVariable("idGroup") long idGroup) {
		return new StatementService().getAllXGroup( idGroup);
	}*/

	/**
	 *	Web service para agregar un statement en el contexto de otro statement.
	 * 
	 */
	@RequestMapping(value = "addExtension", method = RequestMethod.POST, headers = "Accept=application/json", consumes = {MediaType.APPLICATION_JSON_VALUE})
	@ResponseBody
	public void addExtension(@RequestBody String json)
	{

		ObjectMapper mapper = new ObjectMapper();

		long idStatement=0;
		Statement statement = null;
		try
		{
			String id=json.substring(0,json.indexOf("&"));
			json=json.substring(json.indexOf("&")+1);
			idStatement=Long.parseLong(id);
			statement = mapper.readValue(json, Statement.class);
		} catch (JsonParseException e) {
			e.printStackTrace();
		} catch (JsonMappingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Se recupera el actor
		statement.setActor((Actor)new ActorService().getById(statement.getActor().getId()));

		// Se recupera el verbo
		statement.setVerb((Verb)new VerbService().getById(statement.getVerb().getId()));

		new StatementService().create(idStatement, statement);

	}

	/**
	 * Se recuperan todos los statements relacionados
	 * 
	 * @param idStatement
	 * @return
	 */
	@RequestMapping(value = "getExtensions/{idStatement}", method = RequestMethod.GET, headers = "Accept=application/json")
	@ResponseBody
	public List<Statement> getExtensions(@PathVariable("idStatement") long idStatement){
		return new ContextService().getExtensions(idStatement);
	}

	//	/**
	//	 * Se agrega un attachmetn al statement. Este attachement pueder un
	//	 * archivo(pdf, doc, etc), un linek (youtube, una pagina), etc.
	//	 * 
	//	 */
	//	public void addAttachment() {
	//		// NOTE: Dejar para lo ultimo
	//		// TODO
	//	}
	//
	//	/**
	//	 * Se obtiene el atachment.
	//	 */
	//	@RequestMapping(value = "attachment/{idStatement}", method = RequestMethod.GET, headers = "Accept=application/json")
	//	public void getAttchment(@PathParam("idStatement") long idStatement) {
	//		// NOTE: Dejar para lo ultimo
	//		// TODO: Ver el tema de la trasferencia de archivos
	//	}

	private Statement crearStatement(StatementService service, String jsonStatement) throws StatementException, Exception
	{
		Statement statement = service.toStatement(jsonStatement);
		this.recuperarPropiedades(statement);
		statement.setOriginalJSON(jsonStatement);
		statement.setStored(new Date());
		statement.setTimestamp(statement.getStored());
		return statement;
	}

	private void recuperarPropiedades(Statement statement) throws HibernateException, DatabaseException
	{
		ActorService aService = new ActorService();
		VerbService vService = new VerbService();
		GenericService oService =  GenericService.getService(statement.getObject().getClass());
		ActorService tService = new ActorService();

		Actor actor = (Actor)aService.getByProperty("id", statement.getActor().getId());

		Verb verb = (Verb)vService.getByProperty("id", statement.getVerb().getId());

		StatementObject object = (StatementObject)oService.getByProperty("id", statement.getObject().getId());
		
		if(actor != null)
			statement.setActor(actor);
		else
			aService.save(statement.getActor());

		if(verb != null)
			statement.setVerb(verb);
		else
			vService.save(statement.getVerb());

		if(object != null)
			statement.setObject((StatementObject) object);
		else
			oService.save(statement.getObject());
		
		if(statement.getContext() != null)
		{
			if(statement.getContext().getInstructor() != null)
			{
				Actor instructor = (Actor)tService.getByProperty("id", statement.getContext().getInstructor().getId());
				if(instructor != null)
					statement.getContext().setInstructor(instructor);
				else
					tService.save(statement.getContext().getInstructor());				
			}
			
			if(statement.getContext().getTeam() != null)
			{
				Group team = (Group)tService.getByProperty("id", statement.getContext().getTeam().getId());
				if(team != null)
					statement.getContext().setTeam(team);
				else
					tService.save(statement.getContext().getTeam());				
			}
		}
		
		if(statement.getAttachments() != null && !statement.getAttachments().isEmpty())
		{
			GenericService atService =  GenericService.getService(Attachment.class);
			List<Attachment> attachments = new Vector<Attachment>();
			for(Attachment a : statement.getAttachments())
			{
				Attachment at = (Attachment)atService.getById(a.getId());
				if(at == null)
				{
					atService.save(a);
					attachments.add(a);
				}
				else
					attachments.add(at);
			}
			statement.setAttachments(attachments);
		}
	}
}
